﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Young3.FMSearch.Core.Entities;
using System.Diagnostics;
using Young3.FMSearch.Core.Entities.InGame;
using System.Runtime.InteropServices;
using System.Linq.Expressions;

namespace Young3.FMSearch.Core.Managers
{
    internal static class ProcessManager
    {
        public static string FmVersion = null;
        public static FMProcess fmProcess = null;
        public static string FMVersionShort = null;

        public static FMProcess FMProcess
        { 
            get  { return fmProcess; }
            set {fmProcess = value;}
        }

        public static int GetProcessEndPoint(IntPtr process)
        {
            int bytesRead = 0;
            int memoryAddress = 0x6fffffff;
            int num3 = 0x1000000;
            for (int i = 1; i <= 7; i++)
            {
                ReadProcessMemory(process, memoryAddress, 1, out bytesRead);
                while (bytesRead == 0)
                {
                    memoryAddress -= num3;
                    ReadProcessMemory(process, memoryAddress, 1, out bytesRead);
                }
                memoryAddress += num3;
                num3 /= 0x10;
            }
            return memoryAddress;
        }

        #region ReadProcessMemory
        private static byte[] ReadProcessMemory(IntPtr process, int memoryaddressess, uint bytesToRead, out int bytesRead)
        {
            IntPtr ptr;
            byte[] buffer = new byte[bytesToRead];
            ProcessMemoryApi.ReadProcessMemory(process, (IntPtr)memoryaddressess, buffer, bytesToRead, out ptr);
            bytesRead = ptr.ToInt32();
            return buffer;
        }

        public static byte[] ReadProcessMemory(int memoryAdress, uint bytesToRead)
        {
            if (memoryAdress > 0)
            {
                int num;
                if (bytesToRead <= (32 * 1024 * 1024))
                    return ReadProcessMemory(memoryAdress, bytesToRead, out num);
            }
            return new byte[4];
        }

        public static byte[] ReadProcessMemory(int memoryaddressess, uint bytesToRead, out int bytesRead)
        {
            IntPtr ptr;
            byte[] buffer = new byte[bytesToRead];
            ProcessMemoryApi.ReadProcessMemory(FMProcess.Pointer, (IntPtr)memoryaddressess, buffer, bytesToRead, out ptr);
            bytesRead = ptr.ToInt32();
            return buffer;
        }
        #endregion

        #region ReadFromAddress
        public static byte ReadByte(int address)
        {
            return ReadProcessMemory(address, 1)[0];
        }

        public static DateTime ReadDateTime(int address)
        {
            byte[] buffer = ReadProcessMemory(address, 4);
            return ReadDatetime(buffer,0);
        }

        public static Int16 ReadInt16(int address)
        {
            byte[] buffer = ReadProcessMemory(address, 2);
            return ReadInt16(buffer, 0);
        }

        public static int ReadInt32(int address)
        {
            byte[] buffer = ReadProcessMemory(address, 4);
            return ReadInt32(buffer,0);
        }

        public static sbyte ReadSByte(int address)
        {
            return (sbyte)ReadProcessMemory(address, 1)[0];
        }

        public static ushort ReadUShort16(int address)
        {
            byte[] buffer = ReadProcessMemory(address, 2);
            return ReadUshort(buffer, 0);
        }

        public static uint ReadUInt32(int address)
        {
            byte[] buffer = ReadProcessMemory(address, 4);
            return ReadUint32(buffer, 0);
        }

        public static float ReadSingle(int address)
        {
            byte[] buffer = ReadProcessMemory(address, 4);
            return ReadSingle(buffer, 0);
        }

        public static string ReadString(int currentAddress, int? addBufferIndex, bool isRead)
        {
            return ReadString(currentAddress, addBufferIndex, 0, isRead);
        }

        public static string ReadString(int currentAddress, int? addBufferIndex)
        {
            return ReadString(currentAddress, addBufferIndex, 0, false);
        }

        private static Dictionary<string, string> readStringCache = new Dictionary<string, string>();
        public static string ReadString(int currentAddress, int? addBufferIndex, int offset, bool isRead)
        {
            string cacheKey = string.Format("{0}.{1}.{2}.{3}", currentAddress, addBufferIndex ?? -1, offset, isRead);
            if (!readStringCache.ContainsKey(cacheKey))
            {

                if (!isRead)
                    currentAddress = ProcessManager.ReadInt32(currentAddress);

                string str = "";
                int bytesRead = 0;

                byte[] buffer = ProcessManager.ReadProcessMemory(currentAddress, 0x30, out bytesRead);
                if (bytesRead < 0x30)
                {
                    return "";
                }
                if (addBufferIndex.HasValue && addBufferIndex > 0)
                {
                    int memoryAddress = GetAddress(buffer, addBufferIndex.Value);
                    if ((memoryAddress < ProcessManager.FMProcess.StartPoint) ||
                        (memoryAddress > ProcessManager.FMProcess.EndPoint))
                    {
                        return "";
                    }
                    buffer = ProcessManager.ReadProcessMemory(memoryAddress, 0x30, out bytesRead);
                    if (bytesRead < 0x30)
                    {
                        return "";
                    }
                }
                int address = GetAddress(buffer, 12);
                buffer = ProcessManager.ReadProcessMemory(address, 0x60 + Convert.ToUInt32(offset), out bytesRead);

                for (int i = offset; (i < offset + 0x60) && (buffer[i] != 0); i += 2)
                    str = str + Convert.ToChar((int)(buffer[i] + (buffer[i + 1]*0x100)));

                readStringCache.Add(cacheKey, str);
            }
            return readStringCache[cacheKey];
        }
        #endregion

        #region ReadFromBuffer
        private static Dictionary<int, string> stringCache = new Dictionary<int, string>();
        public static string ReadString(ArraySegment<byte> buffer, int offset, int additionalStringOffset)
        {
            int stringPointer = ReadInt32(buffer.Array, offset + buffer.Offset);
            if(!stringCache.ContainsKey(stringPointer))
                stringCache.Add(stringPointer, ReadString(stringPointer, additionalStringOffset, true));

            return stringCache[stringPointer];
        }

        public static UInt32 ReadUint32(byte[] buffer, int offset)
        {
            return BitConverter.ToUInt32(buffer, offset);
        }

        public static ushort ReadUshort(byte[] buffer, int offset)
        {
            return BitConverter.ToUInt16(buffer, offset);
        }

        public static SByte ReadSbyte(byte[] buffer, int offset)
        {
            return (SByte)buffer[offset];
        }

        public static short ReadInt16(byte[] buffer, int offset)
        {
            return BitConverter.ToInt16(buffer, offset);
        }

        public static Int32 ReadInt32(byte[] buffer, int offset)
        {
            return BitConverter.ToInt32(buffer, offset);
        }

        public static Single ReadSingle(byte[] buffer, int offset)
        {
            return BitConverter.ToSingle(buffer, offset);
        }

        public static DateTime ReadDatetime(byte[] buffer, int offset)
        {
            return Converters.DateConverter.FromFmDateTime(((buffer[offset + 3] + (buffer[offset + 2] * 0x100)) + (buffer[offset + 1] * 0x10000)) + (buffer[offset + 0] * 0x1000000));
        }

        public static Byte ReadByte(byte[] buffer, int offset)
        {
            return (byte)buffer[offset];
        }
        #endregion

        #region Write
        public static void WriteByte(byte value, int address)
        {
            byte[] buffer = new byte[] { value };
            WriteProcessMemory(address, buffer, 1);
        }

        public static void WriteDateTime(DateTime value, int address)
        {
            WriteInt32(Converters.DateConverter.ToFmDateTime(value), address, true);
        }

        public static void WriteInt16(int value, int address)
        {
            WriteInt16(value, address, false);
        }

        public static void WriteInt16(int value, int address, bool reverse)
        {
            byte[] buffer = new byte[2];
            if (!reverse)
            {
                buffer[0] = (byte)(value & 0xff);
                buffer[1] = (byte)((value & 0xff00) >> 8);
            }
            else
            {
                buffer[1] = (byte)(value & 0xff);
                buffer[0] = (byte)((value & 0xff00) >> 8);
            }
            WriteProcessMemory(address, buffer, 2);
        }

        public static void WriteInt32(int value, int address)
        {
            WriteInt32(value, address, false);
        }

        public static void WriteInt32(int value, int address, bool reverse)
        {
            byte[] buffer = new byte[4];
            if (!reverse)
            {
                buffer[0] = (byte)(value & 0xff);
                buffer[1] = (byte)((value & 0xff00) >> 8);
                buffer[2] = (byte)((value & 0xff0000) >> 0x10);
                buffer[3] = (byte)((value & 0xff000000L) >> 0x18);
            }
            else
            {
                buffer[3] = (byte)(value & 0xff);
                buffer[2] = (byte)((value & 0xff00) >> 8);
                buffer[1] = (byte)((value & 0xff0000) >> 0x10);
                buffer[0] = (byte)((value & 0xff000000L) >> 0x18);
            }
            WriteProcessMemory(address, buffer, 4);
        }

        public static int WriteProcessMemory(int memoryaddressess, byte[] buffer, uint bytesToWrite)
        {
            IntPtr ptr;
            ProcessMemoryApi.WriteProcessMemory(FMProcess.Pointer, (IntPtr)memoryaddressess, buffer, bytesToWrite, out ptr);
            return ptr.ToInt32();
        }

        public static void WriteString(byte[] value, int address)
        {
            WriteProcessMemory(address, value, 4);
        }

        public static void WriteSByte(sbyte value, int address)
        {
            byte[] buffer = new byte[] { (byte)value };
            WriteProcessMemory(address, buffer, 1);
        }
        #endregion

        public static byte[] StringToByteArray(string inputString)
        {
            List<byte> buffer = new List<byte>();
            foreach (char c in inputString.ToCharArray())
            {
                byte[] twoBytes = { (byte)(c & 0xff), (byte)(c >> 8 & 0xff) };
                buffer.Add(twoBytes[0]);
                buffer.Add(twoBytes[1]);
            }
            return buffer.ToArray();
        }

        public static int GetAddress(byte[] buffer, int index)
        {
            int num = 0;
            try
            {
                num += buffer[index];
                num += buffer[index + 1] * 0x100;
                num += buffer[index + 2] * 0x10000;
                num += buffer[index + 3] * 0x1000000;
            }
            catch
            {
                return 0;
            }
            return num;
        }

        static Dictionary<Type, Func<byte[], int, object>> readFromBufferCache = new Dictionary<Type, Func<byte[], int, object>>();
        private static readonly object ReadFromBufferLock = new object();
        public static object ReadFromBuffer(byte[] bytes, int offset, Type type)
        {
            bool exists;
            Func<byte[], int, object> activeDelegate=null;

            lock (ReadFromBufferLock)
            {
                exists = readFromBufferCache.ContainsKey(type);

                if (exists)
                    activeDelegate = readFromBufferCache[type];
            }

            if (!exists)
            {
                ParameterExpression bts = Expression.Parameter(typeof (byte[]), "bytes");
                ParameterExpression ofs = Expression.Parameter(typeof (int), "offset");
                MethodCallExpression mce = Expression.Call(
                    ObjectManager.GetMethodInfo(type),
                    bts, ofs);
                LambdaExpression le = Expression.Lambda(Expression.Convert(mce, typeof (object)), bts, ofs);

                var compiled = (Func<byte[], int, object>) le.Compile();
                lock (ReadFromBufferLock)
                {
                    readFromBufferCache.Add(type, compiled);
                }
                activeDelegate = compiled;
            }

            return activeDelegate.Invoke(bytes, offset);

        }
    }
}
